#include "searuleset.h"
#include <assert.h>
#include <iostream>
using namespace std;


SEARuleSet::SEARuleSet ()
{
}

SEARuleSet::SEARuleSet(int nbradius, int historySize):nBRadius(nbradius), maxHistorySteps(historySize)
{}

SEARuleSet::SEARuleSet (const SEARuleSet& other)
{
	*this = other;
}

SEARuleSet& SEARuleSet::operator= (const SEARuleSet& other)
{
	if (this == &other)
		return *this;
	this->rule.clear();
	maxHistorySteps = other.maxHistorySteps;
	nBRadius = other.nBRadius;
	for (vector<SEARule>::const_iterator it = other.rule.begin(); it != other.rule.end(); ++it)
		this->rule.push_back(*it);
	return *this;
}

int SEARuleSet::applyOnPixel (const PixelHistory&  pixel) const
{
	int* countOf = new int[CELLSTATES]; //countOf[i] tells how many rule in the object say the value of this pixel should be i in the next time step.
	for (int i = 0; i < CELLSTATES; ++i)
		countOf[i] = 0;
	for (vector<SEARule>::const_iterator it = rule.begin(); it != rule.end(); ++it)
		if (it->applies(pixel))
			countOf[it->getLabel()]++;

	int maxRecommendedIndex = 0;
	for (int i = 1; i < CELLSTATES; ++i)
		if (countOf[i] > countOf[maxRecommendedIndex])
			maxRecommendedIndex = i;
	if (maxRecommendedIndex == 0 && countOf[0] == 0)
	{
		delete[] countOf;
		return -1;
	}

	delete[] countOf;
	return maxRecommendedIndex;

}

int SEARuleSet::fitness (const EightLayerIM& elim, int firstLayer, int lastLayer) const
{

	assert (firstLayer >= 0 && lastLayer >= firstLayer && lastLayer < 7 );
	ImageMatrix result(elim.getRowNum(), elim.getColNum());
	ImageMatrix groundTruth = elim.getImage(lastLayer+1);
#ifdef FITNESS_ALL_PIXELS
	applyOnELIM (elim, firstLayer, lastLayer, result);
	return result.countSimilarity(groundTruth);	
#else
	PixelHistory pixel;
	int row, col;
	int predictedPixelValue;
	int correctGuesses = 0;
	for (int sample = 0; sample < FITNESS_SAMPLE_PIXELS; ++sample)
	{
		row = rand() % MROWS;
		col = rand() % MCOLS;
		pixel.fillFromELIMLayers(elim, firstLayer, lastLayer, row, col, nBRadius);
		predictedPixelValue = applyOnPixel(pixel);
		if (groundTruth.getValue(row,col) == predictedPixelValue)
			++ correctGuesses;
	}
	return correctGuesses;
#endif
}

int SEARuleSet::fitness (const EightLayerIM* elim, int firstLayer, int lastLayer) const
{

	assert (firstLayer >= 0 && lastLayer >= firstLayer && lastLayer < 7 );
	ImageMatrix result(elim->getRowNum(), elim->getColNum());
	ImageMatrix groundTruth = elim->getImage(lastLayer+1);
#ifdef FITNESS_ALL_PIXELS
	applyOnELIM (*elim, firstLayer, lastLayer, result);
	return result.countSimilarity(groundTruth);	
#else
	PixelHistory pixel;
	int row, col;
	int predictedPixelValue;
	int correctGuesses = 0;
	for (int sample = 0; sample < FITNESS_SAMPLE_PIXELS; ++sample)
	{
		row = rand() % MROWS;
		col = rand() % MCOLS;
		pixel.fillFromELIMLayers(*elim, firstLayer, lastLayer, row, col, nBRadius);
		predictedPixelValue = applyOnPixel(pixel);
		if (groundTruth.getValue(row,col) == predictedPixelValue)
			++ correctGuesses;
	}
	return correctGuesses;
#endif
}

void SEARuleSet::advance (EightLayerIM& elim, int firstLayer, int lastLayer)
{
	ImageMatrix result;
	result.setRowNum(elim.getRowNum());
	result.setColNum(elim.getColNum());
	applyOnELIM (elim, firstLayer, lastLayer, result);
	elim.addImageMatrix(result);
}

void SEARuleSet::applyOnELIM (const EightLayerIM& elim, int firstLayer, int lastLayer, ImageMatrix& result) const
{
	assert(elim.getColNum() == result.getColNum() && elim.getRowNum() == result.getRowNum());

	vector<Neighborhood2> currentNB;
	PixelHistory pixel;
	int pixelVal = 0;
	for (int row = 0; row < elim.getRowNum(); ++row)
		for (int col = 0; col < elim.getColNum(); ++col)
		{
			pixel.fillFromELIMLayers(elim, firstLayer, lastLayer, row, col, nBRadius);
			pixelVal = applyOnPixel(pixel);
#ifdef SAVE_BLACKS_ONLY		//in this case the ruleset doest not return 0, only 1 for black and -1 for not defined. a -1 means the pixel will be white.
			if (pixelVal == -1)
				pixelVal = 0;
#endif
			result.setData_noClusterUpdate(row,col, pixelVal);
		}
}


void SEARuleSet::mutate (void)
{
	int r = rand() % (int) rule.size();
	rule[r].mutate();
}

void SEARuleSet::initiateRandom(int ruleNum)
{
	if ((int)rule.size() > 0)
	{
		//cout << "Warning: a non-empty rule set is being randomly initialized." << endl;
		rule.clear();
	}

	SEARule r(nBRadius, maxHistorySteps, -1);
	for (int i = 0; i < ruleNum; ++i)
	{
		r.initiateRandom();
		rule.push_back(r);
	}
}


void SEARuleSet::xOver (const SEARuleSet& parent1, const SEARuleSet& parent2)
{
	if ((int)rule.size() > 0)
	{
		//cout << "Warning: a non-empty rule set is being made again from x-over." << endl;
		rule.clear();
	}
	vector<SEARule>::const_iterator it;
	for (it = parent1.rule.begin(); it != parent1.rule.end(); ++it)
		rule.push_back(*it);
	for (it = parent2.rule.begin(); it != parent2.rule.end(); ++it)
		rule.push_back(*it);
}

int SEARuleSet::getRuleNum() const
{
	return this->rule.size();
}

void SEARuleSet::trimToSize (int ruleNum)
{
	string what("Not Implemented yet");
	exception e(what.data());
	throw e;
}

int SEARuleSet::getTotalPartialConditionNum () const
{
	int result = 0;
	for (vector<SEARule>::const_iterator it = rule.begin(); it != rule.end(); ++ it)
		result += it->getConditionNum();
	return result;
}

int SEARuleSet::getNetInfoSizeInBits () const
{
	//1 bit per label
	//condition: if NBi (8bits) in history hi(3 bits) is/isnot (1 bit) value vi (2bits) then the value is V'i (1 bit) => total of 15 bits per partial condition
	int labelBits = 1;
	int partialConditionBits = 15;
	int totalPartialConditions = getTotalPartialConditionNum();
	return (totalPartialConditions * partialConditionBits) + ((int) rule.size() * labelBits);
}

ostream& operator<< (ostream& stream, const SEARuleSet& ruleset)
{
	stream << "This SEARuleSet has " << ruleset.rule.size() << " rules. nBRadius = " << ruleset.nBRadius << " and maxHistorySteps = " << ruleset.maxHistorySteps << endl;
	for (int i = 0; i < (int) ruleset.rule.size(); ++i)
		stream << i << "- " << ruleset.rule[i] << endl;
	return stream;
}